# coding:utf-8

import os
import time
import random
import json
import math
import uuid
import operator
import hashlib
from datetime import date, timedelta, datetime

import requests
from flask import current_app, request
from sqlalchemy import or_, and_
from marshmallow import ValidationError
from marshmallow.validate import Validator
from redis import WatchError

from app.extensions import db
from app.api.constant import (
    MIN_RATE, MAX_RATE, KF_VIRTUAL_NUM_CACHE_KEY, KF_PHONE_LIST,
    BRONZE_PAID_DISCOUNT, DRAFT_DEVICE_COOKIES_INFO
)
from app.auth.constant import TODAY_DEMAND_COUNT
from app.op.constant import (
    ParticStatus, DemandTag, DemandStatus,
    DemandCate, DemandFinishImgState, FollowType,
    ApplyRealNumberState)
from app.op.models import Partic, Demand, User, Area, Voucher
from app.op.constant import DemandSource
from app.axb.models import VirtualNumber
from app.axb.constant import VirtualNumberValid
from app.api.to_constant import user_pandas, demand_pandas, demand_len
from app.op.utils import get_demand_price
from common.utils import redis_client
from common.demand_util import get_expected_bid_point, demand_bid_count

API = r'http://cx.shouji.360.cn/phonearea.php?number={mobile}'


def generate_captcha(size=4):
    """
    created by chenhj at 2017/8/15
    生成4位数字验证码
    """
    seed = '0123456789'
    ret = [random.choice(seed) for i in range(size)]
    return ''.join(ret)


def generate_demand_info_json(demand, user):
    """
    created by chenhj at 2017/8/23

    """
    from app.api.demand_schemas import (
        cmts_schema, demand_user_schema, partic_follow_list_schema
    )
    from app.api.supplier_schemas import demand_info_schema
    demand_info = demand_info_schema.dump(demand)[0]

    demand_info['expected_bid_point'] = calc_bronze_bid_point(demand)
    demand_info['success_bid_count'] = demand_bid_count(
        demand.id,
        demand.published_count,
        db.session
    )

    cmts_info = cmts_schema.dump(demand.comments)[0]

    user_info = demand_user_schema.dump(demand.owner)[0]
    partic_info, partic = demand.demand_partics_info(user)

    is_training = partic and partic.is_training
    supplier_partic_status = partic and partic.status
    voucher_info = None
    # add by zhouhl 2017-09-15
    # 如果该项目有虚拟号关系，则替换客户手机号为虚拟号
    if partic:

        # 若在待联系、抢单成功阶段, 且使用代金券支付, 则返回代金券信息
        if (partic.status in (
                ParticStatus.NOT_CONTACT.value,
                ParticStatus.SUCCEED.value) and
                partic.voucher_id):
            voucher = Voucher.get_by_id(partic.voucher_id)
            voucher_info = dict(
                id=voucher.id,
                bid_point=voucher.bid_point,
                expired_date=voucher.expired_date.strftime('%F'),
                grant_date=voucher.grant_date.strftime('%F')
            )
        if partic.status in [
            ParticStatus.SUCCEED.value,
            ParticStatus.CALLING.value,
            ParticStatus.NOT_CONTACT.value
        ] and partic.follow_type in (FollowType.LOCKED.value, FollowType.KEEP.value):
            # 未获取真实号码前才需要替换成虚拟号
            if partic.apply_real_number_state == ApplyRealNumberState.NA.value:
                vm_record = VirtualNumber.get_by_args(
                    demand_id=demand.id,
                    valid=VirtualNumberValid.VALID.value,
                    tel_b=user.mobile
                )
                # 替换虚拟号
                if vm_record:
                    user_info['mobile'] = vm_record.tel_x
                    # alter by zhouhl 2017-09-18
                    # 使用虚拟号的客户，项目来源增加‘(百度直推客户)’
                    if demand_info['source'] not in (
                        DemandSource.WEIJIE400.value,
                        DemandSource.SOGOU_SEARCH.value,
                    ):
                        demand_info['source'] += '(百度框架合作直推客户)'
        else:
            user_info['mobile'] = ''
    # 1. 非成功, 非政府项目, 非赠送, 隐藏手机号
    # 2. 虚拟订单, 隐藏手机号
    if (
        (supplier_partic_status not in [ParticStatus.SUCCEED.value, ParticStatus.NOT_CONTACT.value]
         and demand.tag != DemandTag.GOV_RO.value and not is_training) or
        (demand.tag in (DemandTag.VIRT_ONE.value, DemandTag.VIRT_TWO.value))
    ):
        user_info['mobile'] = ''

    # 赠送单非成功或者非待联系, 隐藏手机
    if (is_training
        and supplier_partic_status not in [
            ParticStatus.SUCCEED.value,
            ParticStatus.NOT_CONTACT.value,
            ParticStatus.CALLING.value]):
        user_info['mobile'] = ''

    if not user:
        user_info['mobile'] = ''

    # 跟进信息
    follow_data = Partic.query.filter(
        Partic.demand_id == demand.id,
        Partic.deleted_at == 0,
        Partic.is_training.__eq__(False),
        or_(
            and_(
                Partic.status == ParticStatus.SUCCEED.value,
                Partic.follow_type.in_([
                    FollowType.LOCKED.value, FollowType.KEEP.value
                ]),
                Partic.demand_status.notin_([
                    DemandStatus.DISPATCHING.value,
                    DemandStatus.MASTER_DISPATCHING.value,
                    DemandStatus.BRONZE_MEDAL_DISCOUNT.value
                ])
            ),
            and_(
                Partic.status == ParticStatus.SUCCEED.value,
                Partic.follow_type == FollowType.LOCKED.value,
                Partic.demand_status.in_([
                    DemandStatus.DISPATCHING.value,
                    DemandStatus.MASTER_DISPATCHING.value,
                    DemandStatus.BRONZE_MEDAL_DISCOUNT.value
                ])
            )
        )

    ).join(
        User, User.id == Partic.supplier_id
    ).order_by(
        Partic.bid_win_at.desc()
    ).with_entities(
        Partic.follow_type,
        Partic.follow_msg,
        User.nickname,
        User.mobile,
        User.id.label('user_id')
    )
    follow_records = partic_follow_list_schema.dump(follow_data)[0]

    locked_info = dict()
    for each in follow_records:
        if each.get('follow_type') == FollowType.LOCKED.value:
            locked_info = each
            break

    # 若项目录音为敏感信息, 则只有在抢单后才能看到录音
    if demand.audio_sensitive and supplier_partic_status == 0:
        demand_info['audio'] = ''

    # 判断该项目是否已经有项目完成图片通过审核
    user_id = user.id if user else None
    user_finish_img_state = -1
    user_finish_img_url = None

    demand_finish_img_user_id = None
    demand_finish_img_user_nickname = None
    demand_finish_img_user_com = None
    demand_finish_img_img_url = None
    demand_finish_img_upload_time = None
    tmp_avatar = None

    finish_imgs = list(demand.finish_imgs)
    for i in finish_imgs:
        if i.user_id == user_id:
            user_finish_img_state = i.state
            user_finish_img_url = i.img_url

        if i.state == DemandFinishImgState.CHECKED.value:
            demand_finish_img_user_id = i.owner.id
            demand_finish_img_user_nickname = i.owner.nickname
            demand_finish_img_user_com = i.owner.com or None
            demand_finish_img_img_url = i.img_url
            demand_finish_img_upload_time = str(i.created_datetime)
            tmp_avatar = i.owner.avatar_url

    user_finish_img_info = {
        'state': user_finish_img_state,
        'img': user_finish_img_url,
    }
    demand_finish_img_info = {
        'id': demand_finish_img_user_id,
        'id_': demand_finish_img_user_id,
        'nickname': demand_finish_img_user_nickname,
        'com': demand_finish_img_user_com,
        'img': demand_finish_img_img_url,
        'upload_time': demand_finish_img_upload_time,
        'avatar': tmp_avatar,
    }

    # 如果是派单项目, 则中标待联系时长为15mins
    if demand.status in (
        DemandStatus.DISPATCHING.value, DemandStatus.MASTER_DISPATCHING.value
    ):
        demand_info['win_bid_contact_limit_seconds'] = 900

    price = 0
    if demand.status == DemandStatus.SECOND_ONGOING.value:
        price = get_demand_price(demand)

    return dict(
        demand=demand_info,
        user=user_info,
        leave_bid_point=user.pack.bid_point if user and user.pack else 0,
        cmt_list=cmts_info,
        user_finish_img_info=user_finish_img_info,
        demand_finish_img_info=demand_finish_img_info,
        follow_records=follow_records,
        locked_info=locked_info,
        price=price,
        voucher_info=voucher_info,
        max_voucher_bid_point=Voucher.max_voucher_bid_point(user and user.id),
        **partic_info
    )


def generate_demand_list_json(demands, user):
    """
    created by chenhj at 2017/8/16

    """
    return [generate_demand_info_json(demand, user) for demand in demands]


def generate_to_demand(rate, ip, ip_info):
    """
    created by chenhj at 2017/8/31
    生成TO订单
    """
    if isinstance(rate, bytes):
        rate = rate.decode('utf8')
    rate = float(rate)
    if rate <= 0:
        return

    if rate < 1:
        str_rate = str(rate).split('.')
        # 小数点后位数
        _num = len(str_rate[-1])
        new_rate = int(rate * (10 ** _num))

        # 取时间戳的后面相应对数, 与new_rate对比
        cur_timestamp = str(time.time())
        t_rand = int(cur_timestamp[-_num:])
        if t_rand >= new_rate:
            return

    # 生成一个TO订单
    print('generating a new to demand...')
    prov_id = str(ip_info[2])[:2] + '0000'
    area = Area.get_by_id(prov_id)
    if not area:
        return
    user_list = user_pandas[user_pandas.prov == area.name]
    user_len = len(user_list)

    # 随机调一个用户, 一个项目信息
    user_idx = random.randint(0, user_len - 1)
    demand_idx = random.randint(0, demand_len - 1)

    user_info = user_list.values[user_idx]
    demand_info = demand_pandas.values[demand_idx]

    user = User.get_by_args(mobile=user_info[4])
    if not user:
        print('cannot find {} user'.format(user_info[4]))
        return
    now = int(time.time()) - (10 * 60)
    remark = demand_info[4]
    demand = Demand.create(
        uid=user.id,
        area_id=user.area_id,
        cate=demand_info[1],
        malf=demand_info[3],
        type=demand_info[9],
        surr=demand_info[7],
        size=demand_info[5],
        color=demand_info[2],
        span=demand_info[6],
        prov=user.prov,
        city=user.city,
        addr=user.addr,
        budget=demand_info[0],
        ip=ip,
        tag=demand_info[8],
        status=DemandStatus.ONGOING.value,
        published_at=now,
        created_at=now,
        updated_at=now,
        scan_count=random.randint(10, 20)
    )
    if demand.cate == DemandCate.RENT.value and remark.isdigit():
        days = int(remark)
        start_date = date.today() + timedelta(days=15)
        demand.remark = '租用时间: {start}至{end}'.format(
            start=start_date.strftime('%F'),
            end=(start_date + timedelta(days=days)).strftime('%F')
        )


def get_mobile_prov(mobile):
    """
    created by chenhj at 2017/9/21

    """
    try:
        phone_info = json.loads(requests.get(API.format(mobile=mobile)).content)
        if phone_info['code'] == 0:
            return phone_info['data']['province']
    except:
        pass
    return ''


class EmptyStrValidator(Validator):

    def __init__(self, error=''):
        """
        created by chenhj at 2017/8/23

        """
        self.error = error

    def __call__(self, value):
        """
        判断传入的值, 若为空字符串, 则抛异常
        :param value:
        :return:
        """
        if not value:
            raise ValidationError(self.error)


class RandomDemandCount(object):
    """
    create by ld_wu 2017-09-06
    今日平台项目数
    """

    week_rate_map = {
        0: 1,
        1: 0.9,
        2: 1.2,
        3: 1.13,
        4: 1,
        5: 0.67,
        6: 0.58
    }

    base_num_map = {
        0: 5,
        1: 8,
        2: 3,
        3: 2,
        4: 1,
        5: 2,
        6: 8,
        7: 25,
        8: 55,
        9: 59,
        10: 78,
        11: 64,
        12: 63,
        13: 55,
        14: 73,
        15: 93,
        16: 78,
        17: 71,
        18: 54,
        19: 51,
        20: 49,
        21: 40,
        22: 21,
        23: 7
    }

    op_map = {
        '+': operator.add,
        '-': operator.isub
    }

    @classmethod
    def demand_count(cls):
        now = datetime.now()
        today = now.date()
        store_key = 'demand_count_{}'.format(today)
        with current_app.redis.pipeline() as pipe:
            try:
                pipe.watch(store_key)

                rates = pipe.hgetall(store_key)
                if not rates:
                    rates = cls.gen_rates()
                    pipe.multi()
                    pipe.hmset(store_key, rates)
                    pipe.expire(store_key, 3600 * 24)
                    pipe.execute()
            except WatchError:
                rates = current_app.redis.hgetall(store_key)

            count = 0
            for i in range(now.hour + 1):
                base_num = cls.base_num_map[i]
                op, rate = json.loads(rates[str(i)])
                count += round(cls.op_map[op](base_num, base_num * rate / 100))

            # 根据周末及工作日进行修正
            conf_rate = redis_client.get(TODAY_DEMAND_COUNT) or '1.0'
            conf_rate = float(conf_rate)

            count = round(
                count * cls.week_rate_map.get(now.weekday(), 1) * conf_rate
            )

            return count

    @staticmethod
    def gen_rates():
        return {
            str(i): json.dumps(
                [random.choice(['+', '-']), random.randint(MIN_RATE, MAX_RATE)]
            )
            for i in range(24)
        }


def get_kf_virtual_num_list():
    """
    NOTE by zhouhl 2018-06-01 应该是某一版的官网首页上，展示工程号虚拟号的需求
    created by chenhj at 2017/11/14
    获取跳转到客服电话的虚拟号列表. 并做缓存
    """
    num_list = json.loads(current_app.redis.get(KF_VIRTUAL_NUM_CACHE_KEY) or '[]')
    if not num_list:
        # 读出号码, 写入缓存
        virtual_data = VirtualNumber.query.filter(
            VirtualNumber.tel_a.in_(KF_PHONE_LIST)
        )
        num_list = [x.tel_x for x in virtual_data]
        current_app.redis.set(KF_VIRTUAL_NUM_CACHE_KEY, json.dumps(num_list))
    return num_list


def calc_bronze_bid_point(demand):
    """
    created by chenhj at 2018/4/11
    计算铜牌一口价价格
    """
    expected_bid_point = get_expected_bid_point(demand, demand.area)
    result = math.floor(
        (expected_bid_point or 100) * BRONZE_PAID_DISCOUNT
    )
    # NOTE 最高价为200汇币，前端显示为8折，所以最多只能显示 160
    return min(result, 160)


def draft_extra_handle(response, demand):
    """
    created by chenhj at 2018/4/27
    客户留电接口做一些额外处理：
        给该留电设备种cookies, 有效期1周
        在该cookies仍生效期间, 最多只允许提交max个电话
    note： 但是请求仍正常返回，让用户以为提交成功，只是系统不自动外呼

    @:returns: cookies_val, bool: 是否需要自动外呼
    """
    device_cookies = request.cookies.get(DRAFT_DEVICE_COOKIES_INFO['key'])
    if not device_cookies:
        device_cookies = uuid.uuid4().hex
        response.set_cookie(
            DRAFT_DEVICE_COOKIES_INFO['key'],
            device_cookies,
            max_age=DRAFT_DEVICE_COOKIES_INFO['expires']
        )
        demand.device_cookies = device_cookies
        return True
    demand.device_cookies = device_cookies
    # 不同手机号留电的次数
    demand_cnt = Demand.query.filter(
        Demand.device_cookies == device_cookies
    ).with_entities(Demand.uid.distinct()).count()
    flag = demand_cnt <= DRAFT_DEVICE_COOKIES_INFO['max']
    if not flag:
        demand.status = DemandStatus.INVALID.value
        demand.admin_remark = '7天内留电超过3个不同号码, 后续草稿单自动置为无效'
    return flag


def generate_game_sign(data):
    """
    add by zhouhl 2018-05-28
    计算斗地主接口的sign
    """
    data_keys = sorted(list(data.keys()))
    tmp_string = '&'.join(['{key}={value}'.format(key=key, value=data[key])  for key in data_keys])
    tmp_string += '&secret=' + os.getenv('GAME_SECRET')

    md5 = hashlib.md5()
    md5.update(tmp_string.encode())
    return md5.hexdigest()


if __name__ == '__main__':
    print(generate_captcha())
